#include "util.hpp"

TEST_CASE( MESH_PREFIX "Mesh: Number of edges in different regions" ){
	using hms::exactlyEqual;
	
	auto test = []( const auto& mesh ){
		REQUIRE( mesh.nInnerEdges()    == 17 );
		REQUIRE( mesh.nBoundaryEdges() == 14 );
		REQUIRE( mesh.nEdges()         == 31 );
	};

	SECTION("One ghost layer"){
		MAKE_MESH(3,4,1,1)
		test(mesh);
	}
}

TEST_CASE( MESH_PREFIX "Mesh: number of cells of different kinds" ){
	using hms::exactlyEqual;

	auto testOneLayer = [](const MESH_TYPE& mesh){
		REQUIRE( mesh.nDomainCells() ==  12 );
		REQUIRE( mesh.nGhostCells () ==  14 );
		REQUIRE( mesh.nCells      () ==  26 );
	};

	MAKE_MESH(3,4,1,1)
	testOneLayer(mesh);
}


TEST_CASE( MESH_PREFIX "Mesh: vertices and area" ){
	using hms::approxEqual;
	
	auto test = []( const auto& mesh ){
		REQUIRE( mesh.nVertices() == 20 );
		REQUIRE( mesh.domainArea() ==  Approx(36) );

		REQUIRE( approxEqual(mesh.vertex( 0),  Vector2s{0.0,0} ) );
		REQUIRE( approxEqual(mesh.vertex( 1),  Vector2s{1.5,0} ) );
		REQUIRE( approxEqual(mesh.vertex( 2),  Vector2s{3.0,0} ) );
		REQUIRE( approxEqual(mesh.vertex( 3),  Vector2s{4.5,0} ) );
		REQUIRE( approxEqual(mesh.vertex( 4),  Vector2s{0.0,2} ) );
		REQUIRE( approxEqual(mesh.vertex( 5),  Vector2s{1.5,2} ) );
		REQUIRE( approxEqual(mesh.vertex( 6),  Vector2s{3.0,2} ) );
		REQUIRE( approxEqual(mesh.vertex( 7),  Vector2s{4.5,2} ) );
		REQUIRE( approxEqual(mesh.vertex( 8),  Vector2s{0.0,4} ) );
		REQUIRE( approxEqual(mesh.vertex( 9),  Vector2s{1.5,4} ) );
		REQUIRE( approxEqual(mesh.vertex(10),  Vector2s{3.0,4} ) );
		REQUIRE( approxEqual(mesh.vertex(11),  Vector2s{4.5,4} ) );
		REQUIRE( approxEqual(mesh.vertex(12),  Vector2s{0.0,6} ) );
		REQUIRE( approxEqual(mesh.vertex(13),  Vector2s{1.5,6} ) );
		REQUIRE( approxEqual(mesh.vertex(14),  Vector2s{3.0,6} ) );
		REQUIRE( approxEqual(mesh.vertex(15),  Vector2s{4.5,6} ) );
		REQUIRE( approxEqual(mesh.vertex(16),  Vector2s{0.0,8} ) );
		REQUIRE( approxEqual(mesh.vertex(17),  Vector2s{1.5,8} ) );
		REQUIRE( approxEqual(mesh.vertex(18),  Vector2s{3.0,8} ) );
		REQUIRE( approxEqual(mesh.vertex(19),  Vector2s{4.5,8} ) );
	};

	MAKE_MESH(3,4,1.5,2.0)
	test(mesh);
}

TEST_CASE( MESH_PREFIX "Mesh: boundary patches" ){
	using hms::exactlyEqual;

	auto testOneLayer = [](MESH_TYPE& mesh){
		hms::addStandardBoundaryPatches(mesh);

		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").edges(),  Array3i{ 0,  1,  2    }) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).edges(),  Array3i{12, 13, 14    }) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).edges(),  Array4i{15, 19, 23, 27}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).edges(),  Array4i{18, 22, 26, 30}) );
		
		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").ghostCells(), Array3i{ 1, 2, 3} ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).ghostCells(), Array3i{26,27,28} ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).ghostCells(), Array4i{ 5,10,15,20} ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).ghostCells(), Array4i{ 9,14,19,24} ) );

		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").domainCells(), Array3i{ 6, 7, 8}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).domainCells(), Array3i{21,22,23}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).domainCells(), Array4i{ 6,11,16,21}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).domainCells(), Array4i{ 8,13,18,23}) );
	};

	MAKE_MESH(3,4,1.5,2.0)
	testOneLayer(mesh);
}

TEST_CASE( MESH_PREFIX "Mesh: iterating over cells" ){
	std::vector<Index>
		all, domain, ghost,
		allTest1, domainTest1, ghostTest1,
		allTest2, domainTest2, ghostTest2;
	
	auto test = [&](const MESH_TYPE& mesh){
		auto pushIndex = [&]( std::vector<Index>& vec, Index i ){
			OMP_PRAGMA( critical (push_back) )
			vec.push_back(i);
		};
		mesh.forEachCell( [&](const auto& cell){
			pushIndex( allTest1, cell.index() );
		});
		mesh.forEachCellIndex( [&](Index i){
			pushIndex( allTest2, i );
		});
		mesh.forEachDomainCell( [&](const auto& cell){
			pushIndex( domainTest1, cell.index() );
		});
		mesh.forEachDomainCellIndex( [&](Index i){
			pushIndex( domainTest2, i );
		});
		mesh.forEachGhostCell( [&](const auto& cell){
			pushIndex( ghostTest1, cell.index() );
		});
		mesh.forEachGhostCellIndex( [&](Index i){
			pushIndex( ghostTest2, i );
		});
		std::sort( allTest1.begin(), allTest1.end() );
		std::sort( allTest2.begin(), allTest2.end() );
		std::sort( domainTest1.begin(), domainTest1.end() );
		std::sort( domainTest2.begin(), domainTest2.end() );
		std::sort( ghostTest1.begin(), ghostTest1.end() );
		std::sort( ghostTest2.begin(), ghostTest2.end() );
		REQUIRE( all == allTest1 );
		REQUIRE( all == allTest2 );
		REQUIRE( domain == domainTest1 );
		REQUIRE( domain == domainTest2 );
		REQUIRE( ghost == ghostTest1 );
		REQUIRE( ghost == ghostTest2 );
	};
	
	SECTION( "One ghost layer" ){
		MAKE_MESH(3,4,1,1)

		all = {
			     1,  2,  3,
			 5,  6,  7,  8,  9,
			10, 11, 12, 13, 14,
			15, 16, 17, 18, 19,
			20, 21, 22, 23, 24,
			    26, 27, 28,
		};
		domain = {
			 6,  7,  8,
			11, 12, 13,
			16, 17, 18,
			21, 22, 23
		};
		ghost = {
			     1,  2,  3,
			 5,              9,
			10,             14,
			15,             19,
			20,             24,
			    26, 27, 28,
		};
		test(mesh);
	}
}